1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 */ 6 module code_checker.types; 7 8 @safe: 9 10 /// No guarantee regarding the path. May be absolute, relative, contain a '~'. 11 /// The user of this type must do all the safety checks to ensure that the 12 /// datacontained in valid. 13 struct Path { 14 string payload; 15 alias payload this; 16 } 17 18 /// ditto 19 struct DirName { 20 Path payload; 21 alias payload this; 22 23 pure nothrow @nogc this(string p) { 24 payload = Path(p); 25 } 26 } 27 28 /// ditto 29 struct FileName { 30 pure @nogc nothrow: 31 32 Path payload; 33 alias payload this; 34 35 this(Path p) { 36 payload = p; 37 } 38 39 pure nothrow @nogc this(string p) { 40 payload = Path(p); 41 } 42 } 43 44 /** The path is guaranteed to be the absolute path. 45 * 46 * The user of the type has to make an explicit judgment when using the 47 * assignment operator. Either a `Path` and then pay the cost of the path 48 * expansion or an absolute which is already assured to be _ok_. 49 * This divides the domain in two, one unchecked and one checked. 50 */ 51 struct AbsolutePath { 52 import std.path : expandTilde, buildNormalizedPath; 53 54 Path payload; 55 alias payload this; 56 57 invariant { 58 import std.path : isAbsolute; 59 60 assert(payload.length == 0 || payload.isAbsolute); 61 } 62 63 this(AbsolutePath p) { 64 this.payload = p.payload; 65 } 66 67 this(Path p) { 68 auto p_expand = () @trusted{ return p.expandTilde; }(); 69 // the second buildNormalizedPath is needed to correctly resolve "." 70 // otherwise it is resolved to /foo/bar/. 71 payload = buildNormalizedPath(p_expand).toRealPath; 72 } 73 74 /// Build the normalised path from workdir. 75 this(FileName p, DirName workdir) { 76 auto p_expand = () @trusted{ return p.expandTilde; }(); 77 auto workdir_expand = () @trusted{ return workdir.expandTilde; }(); 78 // the second buildNormalizedPath is needed to correctly resolve "." 79 // otherwise it is resolved to /foo/bar/. 80 payload = buildNormalizedPath(workdir_expand, p_expand).toRealPath; 81 } 82 83 void opAssign(Path p) { 84 payload = p; 85 } 86 87 void opAssign(AbsolutePath p) pure nothrow @nogc { 88 payload = p.payload; 89 } 90 91 FileName opCast(T : FileName)() pure nothrow @nogc { 92 return FileName(payload); 93 } 94 95 Path opCast(T : Path)() @safe pure nothrow const @nogc scope { 96 return payload; 97 } 98 99 string opCast(T : string)() pure nothrow @nogc { 100 return payload; 101 } 102 } 103 104 struct AbsoluteFileName { 105 AbsolutePath payload; 106 alias payload this; 107 108 pure nothrow @nogc this(AbsolutePath p) { 109 payload = p; 110 } 111 } 112 113 struct AbsoluteDirectory { 114 AbsolutePath payload; 115 alias payload this; 116 117 pure nothrow @nogc this(AbsolutePath p) { 118 payload = p; 119 } 120 } 121 122 /** During construction checks that the file exists on the filesystem. 123 * 124 * If it doesn't exist it will throw an Exception. 125 */ 126 struct Exists(T) { 127 AbsolutePath payload; 128 alias payload this; 129 130 this(AbsolutePath p) { 131 import std.file : exists, FileException; 132 133 if (!exists(p)) { 134 throw new FileException("File do not exist: " ~ cast(string) p); 135 } 136 137 payload = p; 138 } 139 140 this(Exists!T p) { 141 payload = p.payload; 142 } 143 144 void opAssign(Exists!T p) pure nothrow @nogc { 145 payload = p; 146 } 147 } 148 149 auto makeExists(T)(T p) { 150 return Exists!T(p); 151 } 152 153 @("shall always be the absolute path") 154 unittest { 155 import std.algorithm : canFind; 156 import std.path; 157 import unit_threaded; 158 159 AbsolutePath(FileName("~/foo")).canFind('~').shouldEqual(false); 160 AbsolutePath(FileName("foo")).isAbsolute.shouldEqual(true); 161 } 162 163 @("shall expand . without any trailing /.") 164 unittest { 165 import std.algorithm : canFind; 166 import unit_threaded; 167 168 AbsolutePath(FileName(".")).canFind('.').shouldBeFalse; 169 AbsolutePath(FileName("."), DirName(".")).canFind('.').shouldBeFalse; 170 } 171 172 @("shall be an instantiation of Exists") 173 nothrow unittest { 174 // the file is not expected to exist. 175 176 try { 177 auto p = makeExists(AbsolutePath(FileName("foo"))); 178 } catch (Exception e) { 179 } 180 } 181 182 private: 183 184 /** Convert a string to the "real path" by resolving all symlinks resulting in an absolute path. 185 186 TODO: optimize 187 This function is very inefficient. It creates a lot of GC garbage. 188 189 trusted: orig_p is a string. A string is assured by the language to be memory 190 safe. Thus this function that operates on strings as input are memory safe for 191 all possible input. 192 */ 193 Path toRealPath(const string orig_p) @trusted { 194 import std.conv : to; 195 import std.path : asAbsolutePath, asNormalizedPath; 196 197 version (Windows) { 198 return path.asAbsolutePath.asNormalizedPath.to!string.Path; 199 } else { 200 import core.sys.posix.stdlib : realpath; 201 import core.stdc.stdlib : free; 202 import std..string : toStringz, fromStringz; 203 204 auto p = orig_p.toStringz; 205 auto absp = realpath(p, null); 206 scope (exit) { 207 if (absp) 208 free(absp); 209 } 210 211 if (absp is null) 212 return orig_p.asAbsolutePath.asNormalizedPath.to!string.Path; 213 else 214 return absp.fromStringz.idup.Path; 215 } 216 }